Skip to content

Implementation of a Parametrised Reduced Functional#241

Open
divijghose wants to merge 7 commits intodolfin-adjoint:masterfrom
divijghose:parametrised_reduced_functional
Open

Implementation of a Parametrised Reduced Functional#241
divijghose wants to merge 7 commits intodolfin-adjoint:masterfrom
divijghose:parametrised_reduced_functional

Conversation

@divijghose
Copy link

@divijghose divijghose commented Feb 4, 2026

Current method to use parameters

The derivative_components optional argument of ReducedFunctional is used after adding parameters to the list of controls, to specify which components are to be zeroed out (by omitting them from derivative_components). This allows the user to update parameters by calling the Reduced Functional while zeroing out the gradient with respect to the parameters.

Parametrised Reduced Functional

ParametrisedReducedFunctional is a subclass of ReducedFunctional with wrapping call and derivative methods, with the parameters as attributes. The parameter_update method is called to update parameters. The parameters are not included in the derivative calculation and the optional argument derivative_components is not required.

…educedFunctional` where parameters can be updated but are not included in the derivative calculations:

1. Adds a `parameter_update` method
2. Parameters are appended at the end of the list of optimization controls, so `derivative_components` is not a required argument.
3. The `derivative` method returns only derivative corresponding to optimization controls.
@JHopeCollins
Copy link
Contributor

In the current implementation ParameterisedReducedFunctional inherits the controls property from ReducedFunctional. I think this means that this will pass when really it shouldn't:

prf = ParameterisedReducedFunctional(functional, user_controls, parameters)
assert len(prf.controls) == len(user_controls) + len(parameters)

This will also be a problem later because the optimisers will expect:

len(prf.derivative()) == len(prf.controls)

when you will actually have (correctly):

len(prf.derivative()) == len(prf.user_controls)

If this is the case then you may need to override the controls property for ParameterisedReducedFunctional so that it returns only what the user thinks are the controls.
However, if I remember correctly how Python inheritance works, that will then mean that when the parent ReducedFunctional accesses self.controls (for example to calculate the derivative here), it will won't see the full list of user_controls + parameter, but will only see user_controls.

To get around this, you may have to instead inherit from the AbstractReducedFunctional base class and just internally create your own ReducedFunctional(functional=functional, controls=user_controls+parameters).

@divijghose
Copy link
Author

In this intermediate implementation, the ParametrisedReducedFunctional inherits from AbstractReducedFunctional. This means that self.controls returns user_controls, which is the required behaviour as discussed above.

However, a lot of the code is duplicated, and, as @colinjcotter suggested, a more efficient way to do this would be to call ReducedFunctional inside ParametrisedReducedFunctional, which will get the required behaviour from self.controls while reusing most of the methods from ReducedFunctional.

…dFunctional` internally:

- Instead of inheriting the `AbstractReducedFunctional` or `ReducedFunctional` classes, `ParametrisedReducedFunctional` simply calls a `ReducedFunctional` object internally and passes the controls and parameters together as `all_controls`
`ParametrisedReducedFunctional` must be a subclass of `AbstractReducedFunctional`

Co-authored-by: Josh Hope-Collins <jhc.jss@gmail.com>
self._controls = Enlist(controls)
self._parameters = Enlist(parameters)
self.n_opt = len(self._controls)
self.derivative_components = tuple(range(self.n_opt)) # Tuple of indices corresponding to optimization controls which are included in derivative calculations.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect that you actually want to ignore this kwarg to the internal ReducedFunctional completely because the ParameterisedReducedFunctional is assuming control of the masking logic.

I think the aim would be to remove derivative_components from the ReducedFunctional interface completely once ParameterisedReducedFunctional is merged because it would be redundant.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that derivative_components is redundant, but since we're passing all_controls (controls + parameters) to ReducedFunctional, can we ignore the kwarg? This implementation shifts the effort from the user to the internal logic.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you want users passing derivative_components to ParameterisedReducedFunctional, and the ParameterisedReducedFunctional won't return the derivative values corresponding to parameters returned by the internal ReducedFunctional anyway so there isn't any point in it zero-ing out those components.

It would be a good test of whether derivative_components can be removed if ParameterisedReducedFunctional works without relying on derivative_components itself.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense.
There is also a difference between how derivatives are returned:
ReducedFunctional with derivative_components returns

[<dControl1>, <dControl2>, ... , <dControln>, 0, 0, ... , 0]

while ParametrisedReducedFunctional returns

[<dControl1>, <dControl2>, ... , <dControln>]

Do you think that would make a difference as far as the user is concerned?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not expect it to make a difference because, as far as I am aware, derivative_components is only used as a stop-gap for ParameterisedReducedFunctional.
But actually removing derivative_components from ReducedFunctional is a job for further down the line so we can have a deeper think about whether there is any difference before then, this is just about the ParameterisedReducedFunctional impl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants